package com.qd.extcamclientv2;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Point;
import android.graphics.Rect;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.MediaController;
import android.widget.TextView;
import android.widget.VideoView;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;

public class PlayerActivity extends MapActivity implements IOnHandlerMessage, OnPreparedListener {
	private static final String TAG = "ExtCamClientV2";
	private void _log(String log) {
		Log.i(TAG, "PlayerActivity: " + log);
	}

	public static final String EXTRA_FILENAME = "EXTRA_FILENAME";
	private static final int META_DATA_SIZE = 24; // SVAL(4)+SPOS(8)+GVAL(4)+GPOS(8)

	private String mFilename = "";
	private Handler mHandler = null;
	private ArrayList<GPSInfo> mGpsInfoList = null;
	private ArrayList<SensorInfo> mSensorInfoList = null;

	private MapView mMapView = null;
	private ExtOverlay mExtOverlay = null;
	private TextView mTvwGps = null;
	private TextView mTvwSensor = null;
	private AcceleratorView mAcceleratorView = null;
	private SensorView mSensorView = null;

	private VideoView mVideoView = null;

	private int mGpsIndex = 0;
	private int mSensorIndex = 0;
	private int mPrevPosition = 0;

	private Rect mMapViewRect = new Rect();
	private Point mMapViewPoint = new Point();

	/* (non-Javadoc)
	 * @see com.google.android.maps.MapActivity#onCreate(android.os.Bundle)
	 */
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.player);

		mFilename = getIntent().getStringExtra(EXTRA_FILENAME);
		_log("mFilename = " + mFilename);
		((TextView) findViewById(R.id.tvw_filename)).setText(mFilename);

		mHandler = new WeakRefHandler((IOnHandlerMessage) this);
		mGpsInfoList = new ArrayList<GPSInfo>();
		mSensorInfoList = new ArrayList<SensorInfo>();

		mMapView = (MapView) findViewById(R.id.mapview);
		mExtOverlay = new ExtOverlay(this);
		mMapView.getOverlays().add(mExtOverlay);

		mTvwGps = (TextView) findViewById(R.id.tvw_gps);
		mTvwSensor = (TextView) findViewById(R.id.tvw_sensor);
		mAcceleratorView = (AcceleratorView) findViewById(R.id.vw_accelerator);
		mSensorView = (SensorView) findViewById(R.id.vw_sensor);

		mVideoView = (VideoView) findViewById(R.id.videoview);
		mVideoView.setMediaController(new MediaController(PlayerActivity.this));
		mVideoView.setOnPreparedListener(PlayerActivity.this);
		mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
			@Override
			public void onCompletion(MediaPlayer mp) {
				finish();
			}
		});

		showSensorView(getSharedPreferences("PlayerActivity", Context.MODE_PRIVATE).getInt("SensorViewType", 1), false);

		new loadAsynkTask().execute();
	}

	/* (non-Javadoc)
	 * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
	 */
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		menu.add(0, 1, 0, "TextView");
		menu.add(0, 2, 0, "AcceleratorView");
		menu.add(0, 3, 0, "SensorView");

		return true;
	}

	@Override
	public void onBackPressed() {
		new AlertDialog.Builder(this).setTitle(R.string.dlg_title_confirm)
		.setMessage(R.string.dlg_msg_exit).setCancelable(false)
		.setNegativeButton(R.string.dlg_btn_cancel, null)
		.setPositiveButton(R.string.dlg_btn_ok, new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				finish();
			}
		})
		.show();

		//super.onBackPressed();
	}

	/* (non-Javadoc)
	 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
	 */
	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case 1:
		case 2:
		case 3:
			showSensorView(item.getItemId() - 1, true);
			return true;
		}

		return false;
	}

	/* (non-Javadoc)
	 * @see android.media.MediaPlayer.OnPreparedListener#onPrepared(android.media.MediaPlayer)
	 */
	@Override
	public void onPrepared(MediaPlayer mp) {
		mp.start();
	}

	/* (non-Javadoc)
	 * @see com.google.android.maps.MapActivity#isRouteDisplayed()
	 */
	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}

	/* (non-Javadoc)
	 * @see com.qd.extcamclientv2.IOnHandlerMessage#handleMessage(android.os.Message)
	 */
	@Override
	public void handleMessage(Message msg) {
		if (-1 == msg.what) {
			mVideoView.setVideoPath(mFilename);
			mMapViewRect.set(0,  0,  mMapView.getWidth(), mMapView.getHeight());
			mHandler.sendEmptyMessage(0);
		} else {
			display(msg.what);
			mHandler.sendEmptyMessage(mVideoView.getCurrentPosition());
		}
	}

	public int getGpsIndex() {
		return mGpsIndex;
	}

	public boolean getGpsPoint(int index, Point point) {
		if (mGpsInfoList.size() > index) {
			double latitude = mGpsInfoList.get(index).getLatitude();
			double longitude = mGpsInfoList.get(index).getLongitude();
			mMapView.getProjection().toPixels(new GeoPoint((int)(latitude * 1E6), (int)(longitude * 1E6)), point);
			if (false == mMapViewRect.contains(point.x, point.y)) {
				point.x = point.y = -1;
			}

			return true;
		}

		return false;
	}

	/**
	 * byte[] 값을  long 값으로 변환 리턴한다.
	 * @param b byte[] 값
	 * @param startIndex byte[]의 시작 인덱스 
	 * @return byte[] 값을  long 값으로 변환 리턴한다.
	 */
	private long byteArrayToLong(byte[] b, int startIndex) {
		ByteBuffer buf = ByteBuffer.wrap(b, startIndex, 8);
		return buf.getLong();
	}

	/**
	 * 지도상에 현재 위치까지의 경로를 화면에 표시한다.
	 * @param position 영상 재생 시간
	 */
	private void display(int position) {
		if (mPrevPosition > position) {
			mSensorIndex = 0;
			mGpsIndex = 0;
		} else if (mPrevPosition == position) {
			return;
		}

		for (int i=mSensorIndex; i<mSensorInfoList.size(); i++) {
			SensorInfo sensorInfo = mSensorInfoList.get(i);
			if (sensorInfo.getTime() > position) {
				if (mSensorIndex != i) {
					mTvwSensor.setText("Sensor : " + sensorInfo.getAzimuth() + ","
							+ sensorInfo.getPitch() + "," + sensorInfo.getRoll()
							+ "," + sensorInfo.getAccels(0) + ","
							+ sensorInfo.getAccels(1) + ","
							+ sensorInfo.getAccels(2));
					mAcceleratorView.setData(sensorInfo.getAzimuth(),
							sensorInfo.getPitch(), sensorInfo.getRoll(),
							sensorInfo.getAccels(0), sensorInfo.getAccels(1),
							sensorInfo.getAccels(2));
					mSensorView.setData(sensorInfo.getAzimuth(),
							sensorInfo.getPitch(), sensorInfo.getRoll(),
							sensorInfo.getAccels(0), sensorInfo.getAccels(1),
							sensorInfo.getAccels(2));
					mSensorIndex = i;
				}
				break;
			}
		}

		for (int i=mGpsIndex; i<mGpsInfoList.size(); i++) {
			GPSInfo gpsInfo = mGpsInfoList.get(i);
			if (gpsInfo.getTime() > position) {
				if (mGpsIndex != i) {
					mTvwGps.setText("Gps : " + gpsInfo.getAltitude() + ","
							+ gpsInfo.getSpeed() + "," + gpsInfo.getUtcTime());
					mGpsIndex = i;
					if (false == mExtOverlay.isTouchDown()) {
						double latitude = mGpsInfoList.get(mGpsIndex).getLatitude();
						double longitude = mGpsInfoList.get(mGpsIndex).getLongitude();
						mMapView.getProjection().toPixels(new GeoPoint((int)(latitude * 1E6), (int)(longitude * 1E6)), mMapViewPoint);
						if (false == mMapViewRect.contains(mMapViewPoint.x, mMapViewPoint.y)) {
							MapController mc = mMapView.getController();
							GeoPoint centerPoint = new GeoPoint((int)(latitude * 1E6), (int)(longitude * 1E6));
							mc.setCenter(centerPoint);
						}
					}
					mMapView.postInvalidate();
				}
				break;
			}
		}

		mPrevPosition = position;
	}

	private void showSensorView(int type, boolean save) {
		_log("showSensorView(" + type + ", " + save + ")");

		switch (type) {
		case 0:
			findViewById(R.id.lnl_text).setVisibility(View.VISIBLE);
			mAcceleratorView.setVisibility(View.INVISIBLE);
			mSensorView.setVisibility(View.INVISIBLE);
			break;
		case 1:
			findViewById(R.id.lnl_text).setVisibility(View.INVISIBLE);
			mAcceleratorView.setVisibility(View.VISIBLE);
			mSensorView.setVisibility(View.INVISIBLE);
			break;
		case 2:
			findViewById(R.id.lnl_text).setVisibility(View.INVISIBLE);
			mAcceleratorView.setVisibility(View.INVISIBLE);
			mSensorView.setVisibility(View.VISIBLE);
			break;
		}

		if (save) {
			SharedPreferences.Editor edit = getSharedPreferences("PlayerActivity", Context.MODE_PRIVATE).edit();
			edit.putInt("SensorViewType", type);
			edit.commit();
		}
	}

	/**
	 * 영상파일에서 위치정보를 추출하여 저장하는 클래스
	 *
	 */
	private class GPSInfo {
		private boolean mIsValid = false;
		private long mTime = 0;
		private double mLatitude = 0;
		private double mLongitude = 0;
		private double mAltitude = 0;
		private float mSpeed = 0;
		private long mUtcTime = 0;

		GPSInfo(String line) {
			if ('#' != line.charAt(0)) {
				String[] tokens = line.replaceAll(":", ",").replaceAll(" ", "").split(",");
				if (tokens != null) {
					for (int i = 0; i < tokens.length; i++) {
						switch (i) {
						case 0:
							mTime = Long.parseLong(tokens[i]);
							break;
						case 1:
							mLatitude = Double.parseDouble(tokens[i]);
							break;
						case 2:
							mLongitude = Double.parseDouble(tokens[i]);
							break;
						case 3:
							mAltitude = Double.parseDouble(tokens[i]);
							break;
						case 4:
							mSpeed = Float.parseFloat(tokens[i]);
							break;
						case 5:
							mUtcTime = (long) Double.parseDouble(tokens[i]);
							break;
						}
					}
					mIsValid = true;
				}
			} else {
				_log(line);
			}
		}

		/**
		 * @return
		 */
		public boolean isValid() {
			return mIsValid;
		}

		/**
		 * @return
		 */
		public long getTime() {
			return mTime;
		}

		/**
		 * @return
		 */
		public double getLatitude() {
			return mLatitude;
		}

		/**
		 * @return
		 */
		public double getLongitude() {
			return mLongitude;
		}

		/**
		 * @return
		 */
		public double getAltitude() {
			return mAltitude;
		}

		/**
		 * @return
		 */
		public float getSpeed() {
			return mSpeed;
		}

		/**
		 * @return
		 */
		public long getUtcTime() {
			return mUtcTime;
		}
	}

	/**
	 * 영상파일에서 센서 정보를 추출하여 저장하는 클래스
	 *
	 */
	private class SensorInfo {
		private boolean mIsValid = false;
		private long mTime = 0;
		private float mAzimuth = 0;
		private float mPitch = 0;
		private float mRoll = 0;
		private float[] mAccels = { 0, 0, 0 };

		SensorInfo(String line) {
			if ('#' != line.charAt(0)) {
				String[] tokens = line.replaceAll(":", ",").replaceAll(" ", "").split(",");
				if (tokens != null) {
					for (int i = 0; i < tokens.length; i++) {
						switch (i) {
						case 0:
							mTime = Long.parseLong(tokens[i]);
							break;
						case 1:
							mAzimuth = Float.parseFloat(tokens[i]);
							break;
						case 2:
							mPitch = Float.parseFloat(tokens[i]);
							break;
						case 3:
							mRoll = Float.parseFloat(tokens[i]);
							break;
						case 4:
							mAccels[0] = Float.parseFloat(tokens[i]);
							break;
						case 5:
							mAccels[1] = Float.parseFloat(tokens[i]);
							break;
						case 6:
							mAccels[2] = Float.parseFloat(tokens[i]);
							break;
						}
					}
					mIsValid = true;
				}
			}
		}

		/**
		 * @return
		 */
		public boolean isValid() {
			return mIsValid;
		}

		/**
		 * @return 시간정보를 리턴한다.
		 */
		public long getTime() {
			return mTime;
		}

		/**
		 * @return 방위각 값을 리턴한다.
		 */
		public float getAzimuth() {
			return mAzimuth;
		}

		/**
		 * @return pitch를 리턴한다.
		 */
		public float getPitch() {
			return mPitch;
		}

		/**
		 * @return  roll 값을 리턴한다.
		 */
		public float getRoll() {
			return mRoll;
		}

		/**
		 * @param idx
		 * @return
		 */
		public float getAccels(int idx) {
			if (mAccels.length > idx) {
				return mAccels[idx];
			} else {
				return Float.NaN;
			}
		}
	}

	/**
	 * 영상파일에서 센서정보 및 위치정보를 추출하기위한 Background 작어 클래스
	 */
	private class loadAsynkTask extends AsyncTask<Void, Integer, Long> {
		private ProgressDialog mProgressDlg = null;

		/**
		 * 
		 */
		public loadAsynkTask() {
		}

		/* (non-Javadoc)
		 * @see android.os.AsyncTask#onPreExecute()
		 */
		@Override
		protected void onPreExecute() {
			_log("onPreExecute()");

			mProgressDlg = ProgressDialog.show(PlayerActivity.this, "File Loading", "Please Wait...", false);
			mProgressDlg.setCancelable(false);

			super.onPreExecute();
		}

		/* (non-Javadoc)
		 * @see android.os.AsyncTask#doInBackground(Params[])
		 * 영상파일에서 센서 정보및 위치정보를 추출한다.
		 */
		@Override
		protected Long doInBackground(Void... params) {
			double maxLati = Double.MIN_VALUE;
			double minLati = Double.MAX_VALUE;
			double maxLongi = Double.MIN_VALUE;
			double minLongi = Double.MAX_VALUE;

			try {
				RandomAccessFile rf = new RandomAccessFile(mFilename, "r");
				FileChannel channel = rf.getChannel();
				ByteBuffer buf = ByteBuffer.allocateDirect(1);
				buf.clear();
				byte[] buffer = new byte[META_DATA_SIZE];
				long fileSize = rf.length();
				if (fileSize - META_DATA_SIZE <= 0) {
					rf.close();
					return null;
				}

				long gpsPos = 0, sensorPos = 0;

				rf.seek(fileSize - META_DATA_SIZE);
				rf.read(buffer);
				if (buffer[0] == 'S' && buffer[1] == 'V' && buffer[2] == 'A' && buffer[3] == 'L') {
					sensorPos = byteArrayToLong(buffer, 4);
				}
				if (buffer[12] == 'G' && buffer[13] == 'V' && buffer[14] == 'A' && buffer[15] == 'L') {
					gpsPos = byteArrayToLong(buffer, 16);
				}

				if (gpsPos == 0) {
					gpsPos = fileSize - META_DATA_SIZE;
				}

				if (sensorPos > 0) {
					rf.seek(sensorPos);
					while (rf.getFilePointer() < gpsPos) {
						SensorInfo sensorInfo = new SensorInfo(readLine(channel, buf));
						if (sensorInfo.isValid()) {
							mSensorInfoList.add(sensorInfo);
						}
					}

					rf.seek(gpsPos);
					while (rf.getFilePointer() < fileSize - META_DATA_SIZE) {
						GPSInfo gpsInfo = new GPSInfo(readLine(channel, buf));
						if (gpsInfo.isValid()) {
							mGpsInfoList.add(gpsInfo);

							if (gpsInfo.getLatitude() > maxLati) {
								maxLati = gpsInfo.getLatitude();
							}
							if (gpsInfo.getLatitude() < minLati) {
								minLati = gpsInfo.getLatitude();
							}

							if (gpsInfo.getLongitude() > maxLongi) {
								maxLongi = gpsInfo.getLongitude();
							}
							if (gpsInfo.getLongitude() < minLongi) {
								minLongi = gpsInfo.getLongitude();
							}
						}
					}
					_log("Latitude(" + minLati + "," + maxLati + ")");
					_log("Longitude(" + minLongi + "," + maxLongi + ")");
				}

				channel.close();
				rf.close();
			} catch (IOException e) {
				e.printStackTrace();
			}

			if (mGpsInfoList.size() > 0) {
				MapController mc = mMapView.getController();
				double latitude = mGpsInfoList.get(0).getLatitude();
				double longitude = mGpsInfoList.get(0).getLongitude();
				GeoPoint centerPoint = new GeoPoint((int)(latitude * 1E6), (int)(longitude * 1E6));
				mc.animateTo(centerPoint);
				mc.zoomToSpan((int)((maxLati - minLati) * 1E6), (int)((maxLongi - minLongi) * 1E6));
			}

			return null;
		}

		/**
		 * @param channel
		 * @param buffer
		 * @return 파일의 한 라인을  문자열로 리턴한다. 
		 */
		private String readLine(FileChannel channel, ByteBuffer buffer) {
			StringBuffer sb = new StringBuffer();
			try {
				while (true) {
					channel.read(buffer);
					buffer.flip();
					byte b = buffer.get();
					sb.append((char) b);
					buffer.clear();
					if ((char) b == (char) '\n') {
						break;
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			return sb.toString();
		}

		/* (non-Javadoc)
		 * @see android.os.AsyncTask#onCancelled()
		 */
		@Override
		protected void onCancelled() {
			if (null != mProgressDlg) {
				mProgressDlg.dismiss();
			}

			super.onCancelled();
		}

		/* (non-Javadoc)
		 * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
		 */
		protected void onPostExecute(Long result) {
			if (null != mProgressDlg) {
				mProgressDlg.dismiss();
			}

			mHandler.sendEmptyMessage(-1);

			super.onPostExecute(result);
		}
	}
}
